﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Media;

namespace Microsoft.Xna.Framework.Audio
{
	/// <summary>
	/// Provides properties, methods, and fields and events for capturing audio data with microphones.
	/// </summary>
	public sealed class Microphone
	{
		#region Fields
		private AudioFormat format;
		private MicrophoneState state;
#if !SILVERLIGHT3
		private MemoryStreamAudioSink _audioSink;
		private CaptureSource _captureSource;
#endif
		private object microphoneLock = new object();
		private static Microphone _defaultMike;
		#endregion

		#region Properties
		/// <summary>
		/// Returns the collection of all currently-available microphones.
		/// </summary>
		public static ReadOnlyCollection<Microphone> All { get; private set; }

		/// <summary>
		/// Gets or sets audio capture buffer duration of the microphone.
		/// </summary>
		public TimeSpan BufferDuration { get; set; }

		/// <summary>
		/// Returns the default attached microphone.
		/// </summary>
		public static Microphone Default
		{
			get { return _defaultMike; }
		}

		/// <summary>
		/// Determines if the microphone is a wired headset or a Bluetooth device.
		/// </summary>
		public bool IsHeadset { get; private set; }

		/// <summary>
		/// Returns the sample rate at which the microphone is capturing audio data.
		/// </summary>
		public int SampleRate
		{
			get { return this.format.SampleRate; }
		}

		/// <summary>
		/// Returns the recording state of the Microphone object.
		/// </summary>
		public MicrophoneState State
		{
			get { return this.state; }
		}
		#endregion

		#region Events
		public event EventHandler<EventArgs> BufferReady;
		#endregion

		#region Constructor
		static Microphone()
		{
			_defaultMike = new Microphone();
#if !SILVERLIGHT3
			_defaultMike._captureSource.AudioCaptureDevice = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();
#endif
			_defaultMike.IsHeadset = true;

			All = new ReadOnlyCollection<Microphone>(new List<Microphone> { _defaultMike });
		}

		internal Microphone()
		{
			this.format = AudioFormat.Create(this.GetSampleRate(), AudioChannels.Mono, 0x10);
#if !SILVERLIGHT3
			this._captureSource = new CaptureSource();

			this._audioSink = new MemoryStreamAudioSink();
			this._audioSink.CaptureSource = _captureSource;
#endif
		}

		private int GetSampleRate()
		{
			return 44100;
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Gets the latest recorded data from the microphone based on the audio capture buffer.
		/// </summary>
		/// <param name="buffer">Buffer, in bytes, containing the captured audio data. The audio format is PCM wave data.</param>
		public int GetData(byte[] buffer)
		{
			if (((buffer == null) || (buffer.Length == 0)) || !this.format.IsAligned(buffer.Length))
			{
				throw new ArgumentException(FrameworkResources.InvalidAudioBuffer);
			}
			return GetData(buffer, 0, buffer.Length);
		}

		/// <summary>
		/// Gets the latest captured audio data from the microphone based on the specified offset and byte count.
		/// </summary>
		/// <param name="buffer">Buffer, in bytes, containing the captured audio data. The audio format is PCM wave data.</param>
		/// <param name="offset">Offset, in bytes, to the starting position of the data.</param>
		/// <param name="count">Amount, in bytes, of desired audio data.</param>
		public int GetData(byte[] buffer, int offset, int count)
		{
			lock (this.microphoneLock)
			{
				if (((buffer == null) || (buffer.Length == 0)) || !this.format.IsAligned(buffer.Length))
				{
					throw new ArgumentException(FrameworkResources.InvalidAudioBuffer);
				}
				if (((offset < 0) || (offset >= buffer.Length)) || !this.format.IsAligned(offset))
				{
					throw new ArgumentException(FrameworkResources.InvalidAudioBufferOffset);
				}
				int num2;
				try
				{
					num2 = offset + count;
				}
				catch (OverflowException)
				{
					throw new ArgumentException(FrameworkResources.InvalidOffsetCountLength);
				}
				if (((count <= 0) || (num2 <= 0)) || (((num2 > buffer.Length) || !this.format.IsAligned(count)) || (this.format.DurationFromSize(count) == TimeSpan.Zero)))
				{
					throw new ArgumentException(FrameworkResources.InvalidOffsetCountLength);
				}
				if (this.State != MicrophoneState.Started)
				{
					return 0;
				}

#if SILVERLIGHT3
				throw new NotSupportedException();
#else
				return _audioSink.GetBytes(buffer, offset, count);
#endif
			}
		}

		/// <summary>
		/// Returns the duration of audio playback based on the size of the buffer.
		/// </summary>
		/// <param name="sizeInBytes">Size, in bytes, of the audio data.</param>
		public TimeSpan GetSampleDuration(int sizeInBytes)
		{
			lock (this.microphoneLock)
			{
				if (sizeInBytes < 0)
				{
					throw new ArgumentException(FrameworkResources.InvalidBufferSize);
				}
				if (sizeInBytes == 0)
				{
					return TimeSpan.Zero;
				}
				return this.format.DurationFromSize(sizeInBytes);
			}
		}

		/// <summary>
		/// Returns the size of the byte array required to hold the specified duration of audio for this microphone object.
		/// </summary>
		/// <param name="duration">TimeSpan object that contains the duration of the audio sample.</param>
		public int GetSampleSizeInBytes(TimeSpan duration)
		{
			int num;
			lock (this.microphoneLock)
			{
				if ((duration.TotalMilliseconds < 0.0) || (duration.TotalMilliseconds > 2147483647.0))
				{
					throw new ArgumentOutOfRangeException("duration");
				}
				if (duration == TimeSpan.Zero)
				{
					return 0;
				}
				try
				{
					return this.format.SizeFromDuration(duration);
				}
				catch (OverflowException)
				{
					throw new ArgumentOutOfRangeException("duration");
				}
			}
			return num;
		}

		/// <summary>
		/// Starts microphone audio capture.
		/// </summary>
		public void Start()
		{
#if SILVERLIGHT3
			throw new NotSupportedException();
#else
			lock (this.microphoneLock)
			{
				if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
				{
					_captureSource.Start();
					this.state = MicrophoneState.Started;
				}
				else
				{
					throw new NoMicrophoneConnectedException(String.Empty, new UnauthorizedAccessException(FrameworkResources.UserDeniedAccess));
				}
			}
#endif
		}

		/// <summary>
		/// Stops microphone audio capture.
		/// </summary>
		public void Stop()
		{
#if SILVERLIGHT3
			throw new NotSupportedException();
#else
			lock (this.microphoneLock)
			{
				_captureSource.Stop();
				this.state = MicrophoneState.Stopped;
			}
#endif
		}
		#endregion
	}
}
